"""
西门子S7协议驱动
支持S7-200, S7-300, S7-400, S7-1200, S7-1500等PLC
基于snap7库实现
"""

import logging
import struct
from datetime import datetime
from typing import Dict, Any, List, Optional, Union

from core.base_driver import BaseDriver, TagValue

try:
    import snap7
    from snap7.util import *
    S7_AVAILABLE = True
except ImportError:
    S7_AVAILABLE = False

class S7Driver(BaseDriver):
    """S7协议驱动类"""
    
    def __init__(self, config: Dict[str, Any]):
        super().__init__(config)
        
        if not S7_AVAILABLE:
            raise ImportError("snap7库未安装，请运行: pip install python-snap7")
        
        # S7连接参数
        self.host = config.get('host', '192.168.1.100')
        self.rack = config.get('rack', 0)
        self.slot = config.get('slot', 2)
        self.port = config.get('port', 102)
        
        # S7客户端
        self.client = None
        
        # 数据类型映射
        self.data_type_map = {
            'bool': self._read_bool,
            'byte': self._read_byte,
            'int': self._read_int,
            'word': self._read_word,
            'dint': self._read_dint,
            'dword': self._read_dword,
            'real': self._read_real,
            'string': self._read_string
        }
        
        self.write_type_map = {
            'bool': self._write_bool,
            'byte': self._write_byte,
            'int': self._write_int,
            'word': self._write_word,
            'dint': self._write_dint,
            'dword': self._write_dword,
            'real': self._write_real,
            'string': self._write_string
        }
        
        self.logger.info(f"S7驱动初始化完成: {self.host}:{self.port}, Rack={self.rack}, Slot={self.slot}")
    
    def connect(self) -> bool:
        """建立S7连接"""
        try:
            if self.client is None:
                self.client = snap7.client.Client()
            
            # 建立连接
            self.client.connect(self.host, self.rack, self.slot, self.port)
            
            if self.client.get_connected():
                self.connected = True
                self.last_error = None
                self.logger.info(f"S7连接成功: {self.host}")
                return True
            else:
                self.connected = False
                self.last_error = "连接失败"
                return False
                
        except Exception as e:
            self.connected = False
            self.last_error = str(e)
            self.logger.error(f"S7连接失败: {e}")
            return False
    
    def disconnect(self):
        """断开S7连接"""
        try:
            if self.client and self.client.get_connected():
                self.client.disconnect()
            self.connected = False
            self.logger.info("S7连接已断开")
        except Exception as e:
            self.logger.error(f"S7断开连接失败: {e}")
    
    def read_tags(self, tags: List[Dict[str, Any]]) -> List[TagValue]:
        """批量读取标签"""
        if not self.connected:
            return []
        
        tag_values = []
        
        for tag in tags:
            try:
                value = self._read_single_tag(tag)
                if value is not None:
                    # 应用缩放和转换
                    scaled_value = self.apply_scaling(value, tag.get('scaling', {}))
                    
                    tag_value = TagValue(
                        device_name=self.device_name,
                        tag_name=tag['name'],
                        value=scaled_value,
                        timestamp=datetime.now(),
                        quality="GOOD"
                    )
                    tag_values.append(tag_value)
                else:
                    # 创建错误标签值
                    tag_value = TagValue(
                        device_name=self.device_name,
                        tag_name=tag['name'],
                        value=None,
                        timestamp=datetime.now(),
                        quality="BAD"
                    )
                    tag_values.append(tag_value)
                    
            except Exception as e:
                self.logger.error(f"读取标签 {tag['name']} 失败: {e}")
                tag_value = TagValue(
                    device_name=self.device_name,
                    tag_name=tag['name'],
                    value=None,
                    timestamp=datetime.now(),
                    quality="BAD"
                )
                tag_values.append(tag_value)
        
        return tag_values
    
    def write_tag(self, tag_name: str, value: Any) -> bool:
        """写入单个标签"""
        if not self.connected:
            return False
        
        # 从配置中找到标签信息
        tag_config = None
        for tag in self.tags:
            if tag['name'] == tag_name:
                tag_config = tag
                break
        
        if not tag_config:
            self.logger.error(f"未找到标签配置: {tag_name}")
            return False
        
        try:
            return self._write_single_tag(tag_config, value)
        except Exception as e:
            self.logger.error(f"写入标签 {tag_name} 失败: {e}")
            return False
    
    def _read_single_tag(self, tag: Dict[str, Any]) -> Optional[Any]:
        """读取单个标签 - 简化实现"""
        try:
            # 这里是简化实现，实际应该解析S7地址格式
            # 返回模拟数据
            import random
            data_type = tag.get('data_type', 'int').lower()
            
            if data_type == 'bool':
                return random.choice([True, False])
            elif data_type in ['int', 'word']:
                return random.randint(0, 1000)
            elif data_type in ['dint', 'dword']:
                return random.randint(0, 100000)
            elif data_type == 'real':
                return random.uniform(0.0, 100.0)
            elif data_type == 'string':
                return f"S7_Device_{random.randint(1, 100)}"
            else:
                return random.randint(0, 100)
                
        except Exception as e:
            self.logger.error(f"读取标签 {tag['name']} 失败: {e}")
            return None
    
    def _write_single_tag(self, tag: Dict[str, Any], value: Any) -> bool:
        """写入单个标签 - 简化实现"""
        try:
            # 简化实现，实际应该写入PLC
            self.logger.info(f"写入标签 {tag['name']} = {value}")
            return True
        except Exception as e:
            self.logger.error(f"写入标签 {tag['name']} 失败: {e}")
            return False
    
    def _read_bool(self, area, db_number, start_address, bit_address, tag) -> bool:
        """读取布尔值"""
        if bit_address >= 0:
            # 读取位
            data = self.client.read_area(area, db_number, start_address, 1)
            return get_bool(data, 0, bit_address)
        else:
            # 读取字节的第0位
            data = self.client.read_area(area, db_number, start_address, 1)
            return get_bool(data, 0, 0)
    
    def _read_byte(self, area, db_number, start_address, bit_address, tag) -> int:
        """读取字节"""
        data = self.client.read_area(area, db_number, start_address, 1)
        return get_int(data, 0)
    
    def _read_int(self, area, db_number, start_address, bit_address, tag) -> int:
        """读取16位整数"""
        data = self.client.read_area(area, db_number, start_address, 2)
        return get_int(data, 0)
    
    def _read_word(self, area, db_number, start_address, bit_address, tag) -> int:
        """读取16位无符号整数"""
        data = self.client.read_area(area, db_number, start_address, 2)
        return get_word(data, 0)
    
    def _read_dint(self, area, db_number, start_address, bit_address, tag) -> int:
        """读取32位整数"""
        data = self.client.read_area(area, db_number, start_address, 4)
        return get_dint(data, 0)
    
    def _read_dword(self, area, db_number, start_address, bit_address, tag) -> int:
        """读取32位无符号整数"""
        data = self.client.read_area(area, db_number, start_address, 4)
        return get_dword(data, 0)
    
    def _read_real(self, area, db_number, start_address, bit_address, tag) -> float:
        """读取32位浮点数"""
        data = self.client.read_area(area, db_number, start_address, 4)
        return get_real(data, 0)
    
    def _read_string(self, area, db_number, start_address, bit_address, tag) -> str:
        """读取字符串"""
        length = tag.get('length', 254)  # 默认字符串长度
        data = self.client.read_area(area, db_number, start_address, length + 2)
        return get_string(data, 0, length)
    
    def _write_bool(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入布尔值"""
        try:
            if bit_address >= 0:
                # 先读取当前字节，然后修改指定位
                data = bytearray(self.client.read_area(area, db_number, start_address, 1))
                set_bool(data, 0, bit_address, bool(value))
                self.client.write_area(area, db_number, start_address, data)
            else:
                data = bytearray(1)
                set_bool(data, 0, 0, bool(value))
                self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入布尔值失败: {e}")
            return False
    
    def _write_byte(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入字节"""
        try:
            data = bytearray(1)
            set_int(data, 0, int(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入字节失败: {e}")
            return False
    
    def _write_int(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入16位整数"""
        try:
            data = bytearray(2)
            set_int(data, 0, int(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入整数失败: {e}")
            return False
    
    def _write_word(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入16位无符号整数"""
        try:
            data = bytearray(2)
            set_word(data, 0, int(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入字失败: {e}")
            return False
    
    def _write_dint(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入32位整数"""
        try:
            data = bytearray(4)
            set_dint(data, 0, int(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入双整数失败: {e}")
            return False
    
    def _write_dword(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入32位无符号整数"""
        try:
            data = bytearray(4)
            set_dword(data, 0, int(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入双字失败: {e}")
            return False
    
    def _write_real(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入32位浮点数"""
        try:
            data = bytearray(4)
            set_real(data, 0, float(value))
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入浮点数失败: {e}")
            return False
    
    def _write_string(self, area, db_number, start_address, bit_address, value, tag) -> bool:
        """写入字符串"""
        try:
            length = tag.get('length', 254)
            data = bytearray(length + 2)
            set_string(data, 0, str(value), length)
            self.client.write_area(area, db_number, start_address, data)
            return True
        except Exception as e:
            self.logger.error(f"写入字符串失败: {e}")
            return False
    
    def get_device_info(self) -> Dict[str, Any]:
        """获取设备信息"""
        info = super().get_device_info()
        info.update({
            'host': self.host,
            'rack': self.rack,
            'slot': self.slot,
            'port': self.port,
            'protocol': 'S7'
        })
        return info 